// import React, { useState, useMemo, useCallback, useContext, createContext, memo, useRef } from 'react';
import React, { memo, createContext, forwardRef } from 'react';
import ReactDOM from 'react-dom';
// 创建Context上下文
const ReactContext = createContext();
const { Provider } = ReactContext;


import { isFunction } from './helpers';

export let hookIndex = 0;
let hookStates = [];

/**
 * useState
 * @param {*} initialState 
 */
function useState(initialState) {
    hookStates[hookIndex] = hookStates[hookIndex] || (hookStates[hookIndex] = (isFunction(initialState) ? initialState() : initialState));
    let i = hookIndex;
    function setState(newState) {
        console.log(i);
        // 1. 更新值
        hookStates[i] = newState;
        // 2. 刷新组件
        render();
    }
    // 返回的时候要hookIndex要加1，不然其他的hook就会乱
    return [hookStates[hookIndex++], setState]

}
/**
 * useMemo
 * @param {*} memoObj 
 * @param {*} deps 
 */
function useMemo2(memoObj, deps) {
    if (!hookStates[hookIndex]) { // 首次调用
        const obj = memoObj();
        hookStates[hookIndex] = [obj, deps];
    } else { // 不是首次调用
        const [obj, oldDeps] = hookStates[hookIndex];
        const isChange = oldDeps.every((item, i) => item === deps[i]); //只有当依赖项的每一项都不变的时候，才会为true 
        if (!isChange) { // 依赖改变了
            const obj = memoObj();
            hookStates[hookIndex] = [obj, deps];
        } else { // 依赖没有改变，但是hookIndex也要+1, 才能让匹配到下一次的其他钩子
            hookIndex++;
            return obj;
        }
    }
    // 返回hookIndex要加一
    return hookStates[hookIndex++][0];
}

/**
 * useCallback
 * @param {*} cb 
 * @param {*} deps 
 */
function useCallback2(cb, deps) {
    if (!hookStates[hookIndex]) { // 首次调用
        // 直接放入数组即可
        hookStates[hookIndex] = [cb, deps];
    } else { // 不是首次调用
        const [oldCb, oldDeps] = hookStates[hookIndex];
        // 判断依赖项是否改变
        const isChange = oldDeps.every((item, i) => item === hookStates[i]);
        if (!isChange) { // 改变了
            hookStates[hookIndex] = [cb, deps];
        } else { // 没有改变
            hookIndex++;
            return oldCb;
        }
    }
    return hookStates[hookIndex++][0]
}

/**
 * useContext
 * @param {*} ReactContext 
 */
function useContext2(ReactContext) {
    // Provider的value属性在_currentValue上面
    return ReactContext._currentValue;
}

/**
 * useRef
 * @param {*} initial 
 */
function useRef(initial) {
    if (!hookStates[hookIndex]) { // 首次渲染
        hookStates[hookIndex] = { current: initial };
    }

    return hookStates[hookIndex++];
}


/**
 * useImperativeHandle
 * @param {*} ref 
 * @param {*} init 
 */
function useImperativeHandle(ref, init) {
    ref.current = init();
}

/**
 * useReducer
 * @param {*} reducer 
 * @param {*} initialState 
 * @param {*} init 
 */
function useReducer(reducer, initialState, init) {
    // 如果已经有了，那么取有的值，如果还没有，那么
    hookStates[hookIndex] = hookStates[hookIndex] ? hookStates[hookIndex] : (init && isFunction(init) && init(initialState) || initialState);
    let i = hookIndex; // 保存当前的hookIndex 更新的时候用得到， 利用闭包的特性
    function dispatch(action) {
        // 传入当前的值，得到新的state
        hookStates[i] = reducer(hookStates[i], action);
        render(); // 刷新组件
    }

    return [hookStates[hookIndex++], dispatch]
}


/**
 * useEffect
 * @param {*} cb 
 * @param {*} deps 
 */
function useEffect(cb, deps) {
    // 默认先执行一次cb
    if (!hookStates[hookIndex]) {
        let nextCallFn;
        setTimeout(() => {
            nextCallFn = cb()
        })
        hookStates[hookIndex] = [nextCallFn, deps];
        // 这个执行在渲染后执行的
        hookIndex++;
    } else {
        const [oldFn, oldDeps] = hookStates[hookIndex];
        const isChange = (deps && oldDeps && deps.every((item, i) => item === oldDeps[i])) || false;  // 如果不传依赖项，默认都是false
        console.log(isChange)
        if (!isChange) { // 变化了
            oldFn && oldFn(); // 执行上一次执行cb返回的函数
            // 重新生成新的
            let nextCallFn;
            setTimeout(() => {
                nextCallFn = cb()
            })
            hookStates[hookIndex] = [nextCallFn, deps];
            // 这个执行在渲染后执行的
            hookIndex++;
        } else { // 没有变化
            // hookIndex直接加一即可
            hookIndex++;
        }
    }

}


function useLayoutEffect(cb, deps) {
    if (!hookStates[hookIndex]) {
        // 首次调用
        let nextCallFn;
        Promise.resolve().then(() => {
            nextCallFn = isFunction(cb) && cb();
        })
        hookStates[hookIndex] = [nextCallFn, deps];
        hookIndex++;
    } else {
        let [oldFn, oldDeps] = hookStates[hookIndex];
        const isChange = (deps && oldDeps && deps.every((item, i) => item === oldDeps[i])) || false;
        if (!isChange) {
            // 依赖项改变了
            isFunction(oldFn) && oldFn();
            // 重新调用新的cb
            let nextCallFn;
            Promise.resolve().then(() => {
                nextCallFn = isFunction(cb) && cb();
            })
            hookStates[hookIndex] = [nextCallFn, deps];
            hookIndex++;
        } else {
            hookIndex++
        }
    }
}



const ForwardRef = forwardRef(Input);


function Name(props) {
    console.log('name render');
    return <>
        {/* <div>{props.name}</div> */}
        <button onClick={() => props.change('xixi')}>setName</button>
    </>
}


function Input(props, ref) {
    const inpRef = useRef();
    // 命令形式，只给父节点暴露一个focus方法，防止父节点乱使用input的ref
    useImperativeHandle(ref, function () {
        return {
            focus() {
                console.log(inpRef, 'fjdlsakjfklds')
                inpRef.current.focus()
            }
        }
    })
    // console.log(inpRef);
    // 方法1
    // return <input type="text" ref={props.inpRef} />
    return <>
        <input type="text" ref={inpRef} />
        <button onClick={() => { console.log(inpRef) }}>jfkdjflsd</button>
    </>
}


// 如果组件一样，就不会重新渲染组件
const WithName = memo(Name);

let t;
function App() {
    // const [name, setName] = useState('hchlq');
    const [count, setCount] = useState(() => 0);
    //! 注意要放依赖数组，否则每次都返回不同的对象。只有当依赖项改变的时候，才会重新函数返回新对象
    // useMemo
    // const obj = useMemo(() => ({ name: 'hchlq' }), []);
    // 如果依赖项不变的话，这里除了首次渲染之外，t === obj都为true
    // console.log(t === obj);
    // t = obj;

    // useCallback
    // const cb = useCallback(() => { console.log('aa') }, []);
    // console.log(t === cb);
    // t = cb;


    // useContext
    // const context = useContext(ReactContext);
    // console.log(context);


    // useRef
    // const ref = useRef(0);
    // console.log(ref);
    // ref.current = 'jflksa';
    // const ref = useRef();

    // useReducer
    function reducer(state, action) {
        switch (action.type) {
            case 'add':
                return { ...state, count: state.count + 1 }
            case 'minus':
                return { ...state, count: state.count - 1 }
            default:
                return state;
        }
    }


    // useEffect(() => {
    //     console.log('effect')
    //     return function () {
    //         console.log('依赖项变化啦!!!!!');
    //     }
    // }, [])

    // useLayoutEffect
    useLayoutEffect(() => {
        console.log('useLayoutEffect')
        return function () {
            console.log('依赖项变化啦!!!!!');
        }
    })
    const [state, dispatch] = useReducer(reducer, { count: 1 }, function (state) { console.log(state); return { ...state, count: 3 } });// 第三个参数传入的是函数，参数是第二个参数
    return <>
        <div>0</div>
        <div>{state.count}</div>
        <button onClick={() => dispatch({ type: 'add' })}>+1</button>
        {/* <div>{count}</div> */}
        {/* <button onClick={() => setCount(count + 1)}>+</button> */}


        {/* 可以传ref到函数组件的两种方法 */}
        {/* 1. 使用forwardRef包裹函数组件，将返回组件传过来，原理就是传过来一个类组件，因为类组件可以传ref */}
        {/* 2. 传的属性名不叫ref  */}
        {/* 1 */}
        {/* <Input inpRef={ref}></Input> */}
        {/* 2 */}
        {/* <ForwardRef ref={ref}></ForwardRef>
        <button onClick={() => { console.log(ref); ref.current.focus() }}>获得input的焦点</button> */}

        {/* 调用setCount Name组件也会更新，应该不会更新才对*/}
        {/* 解决方案 */}
        {/* 1. 使用memo包裹Name组件，然后这里使用包裹或返回的组件 */}
        {/* 2. 使用useMemo包裹对象起来 将对象传过去即可 */}
        {/* <WithName change={cb} /> */}
    </>
}


/**
 * 简单版的hook, 刷新组件暂时先调用render
 */
const obj = { a: 'aa', b: 'bb' }
export function render() {
    // 重新渲染的时候，hookIndex要置为0
    hookIndex = 0;
    ReactDOM.render(
        <Provider value={obj}>
            <App />
        </Provider>,
        document.getElementById('root'));
}


render();



